using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;



namespace OhioState.Graphics.Proxy
{

    /// <summary>
    /// Volume class contains information of the file to be loaded like filename,type, dimensions
    /// Currently supports RAW and VOL format.
    /// </summary>
    [Serializable()]
    public class Volume : Texture3D
    {
        private int xDim, yDim, zDim;
        private String fileName;
        private bool isRaw;
        /// <summary>
        /// File Extension
        /// </summary>
        protected String extension;
        private static TexelFormat defaultTexelFormat = new TexelFormat();

        //to support procedurally (on the fly) generated volumes 
        //(not only the ones loaded from file)
        private byte[] _byteData;
        public byte[] ByteData
        {
            set { this._byteData = value; }
            get { return _byteData; }
        }
        public void setDimensions(int x, int y, int z)
        {
            xDim = x;
            yDim = y;
            zDim = z;
        }
        //

        /// <summary>
        /// Volume Constructor
        /// </summary>
        /// <param name="filename">filename</param>
        /// <param name="xDim">x Dim</param>
        /// <param name="yDim">y Dim</param>
        /// <param name="zDim">z Dim</param>
        /// <param name="format">Texel Format</param>
        public Volume(String filename, int xDim, int yDim, int zDim, TexelFormat format)
            : base("temp", xDim, yDim, zDim, format)
        {
            this.fileName = filename;
            this.extension = System.IO.Path.GetExtension(filename);
            this.Name = Path.GetFileName(filename);
            this.isRaw = false;
            if (extension == ".raw")
                this.isRaw = true;
            this.xDim = xDim;
            this.yDim = yDim;
            this.zDim = zDim;
            TexelFormat texFormat = format;
            texFormat.Format = ImageFormat.ALPHA;
            if (extension == ".dat")
            {
                texFormat.Type = ByteFormat.SHORT;
                //texFormat.Format = ImageFormat.RGBA;
            }

            this.TexelType = texFormat;

            ReadHeader();

            this._width = this.xDim;
            this._height = this.yDim;
            this._depth = this.zDim;
        }
        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="filename">filename</param>
        public Volume(String filename)
            : this(filename, 0, 0, 0, defaultTexelFormat)
        {
        }
        
        /// <summary>
        /// Clone - make a duplicate copy of this transfer function and return it 
        /// </summary>
        /// <returns></returns>
        public override TextureProxy Clone()
        {
            Volume volume;
            volume = new Volume(fileName, xDim, yDim, zDim, this.TexelType);
            return volume;
        }

        #region File operations
                
        /// <summary>
        /// LoadFromFile - static function that loads a Volume from a file
        ///  using binary deserialization.  Returns null if these is a problem.
        /// </summary>
        /// <param name="filename">filename</param>
        /// <returns>volume</returns>
        public static Volume LoadFromFile(String filename)
        {
            Volume obj;

            Stream stream = File.Open(filename, FileMode.Open);
            BinaryFormatter formatter = new BinaryFormatter();

            try
            {
                obj = (Volume)formatter.Deserialize(stream);
            }
            catch
            {
                obj = null;
            }
            stream.Close();

            return obj;
        }

                
        /// <summary>
        /// SaveToFile - function that saves a transfer function to a file
        ///  using binary serialization.  This is not static and is simply called on the
        ///  object that needs to be saved
        /// </summary>
        /// <param name="filename">filename</param>
        public void SaveToFile(String filename)
        {
            //
            // Make a clone to save.  The marshalling done by remoting causes problems with 
            //  serialization.
            //
            Volume obj = (Volume)this.Clone();

            Stream stream = File.Open(filename, FileMode.Create);
            BinaryFormatter formatter = new BinaryFormatter();

            formatter.Serialize(stream, obj);
            stream.Close();
        }

        private String ToByteToString(byte[] intValue)
        {
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < intValue.Length; i++)
            {
                sb.Append((char)intValue[i]);
            }
            return sb.ToString().Trim();
        }

        private byte[] ReadTilWhiteSpace(ref BinaryReader bin)
        {
            byte[] args = new byte[5];
            for (int i = 0; i < 5; i++)
            {
                args[i] = bin.ReadByte();
                
                if (args[i] == 32 || (bin.PeekChar() != 32 && (bin.PeekChar() < 48 || bin.PeekChar() > 57)))
                    return args;

            }
            return args;
        }

        private void ReadHeader()
        {
            if (!isRaw)
            {
                if (!File.Exists(this.fileName))
                {
                    gLogger.Instance.writeLog("Cannot open file: ");
                    gLogger.Instance.writeLog(this.fileName);
                    gLogger.Instance.writeLog("\n");
                }

                else
                {
                    using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read))
                    {
                        BinaryReader binReader = new BinaryReader(fs);
                        binReader.BaseStream.Position = 0;
                        try
                        {
                            if (extension == ".vol")
                            {
                                xDim = Convert.ToInt32(ToByteToString(ReadTilWhiteSpace(ref binReader)));
                                yDim = Convert.ToInt32(ToByteToString(ReadTilWhiteSpace(ref binReader)));
                                zDim = Convert.ToInt32(ToByteToString(ReadTilWhiteSpace(ref binReader)));
                            }
                            else if (extension == ".dat")
                            {
                                xDim = binReader.ReadUInt16();
                                yDim = binReader.ReadUInt16();
                                zDim = binReader.ReadUInt16();
                            }

                            //int n_pow;
                            //int x_Dim, y_Dim, z_Dim;
                            //n_pow = (int)(System.Math.Ceiling(System.Math.Log((double)xDim) / System.Math.Log(2.0)));
                            //x_Dim = 2 << (n_pow - 1);
                            //n_pow = (int)(System.Math.Ceiling(System.Math.Log((double)yDim) / System.Math.Log(2.0)));
                            //y_Dim = 2 << (n_pow - 1);
                            //n_pow = (int)(System.Math.Ceiling(System.Math.Log((double)zDim) / System.Math.Log(2.0)));
                            //z_Dim = 2 << (n_pow - 1);

                            //xDim = x_Dim;
                            //yDim = y_Dim;
                            //zDim = z_Dim;

                        }
                        catch (System.FormatException e)
                        {
                            gLogger.Instance.writeLog(e.ToString() + "\n");
                        }
                        finally
                        {
                            binReader.Close();
                        }
                    }
                }
            }
        }
        #endregion

        
        
        
        /// <summary>
        /// Reads the texture and returns texture data and the dimensions 
        /// </summary>
        /// <param name="xDim">x Dim</param>
        /// <param name="yDim">y Dim</param>
        /// <param name="zDim">z Dim</param>
        /// <returns>byte array</returns>
        public override byte[] GetTextureByte(out int xDim, out int yDim, out int zDim)
        {
            if (_byteData != null)
            {
                //we assume that if _byteData is not null, dimensions 
                //have also been set
                xDim = this.xDim;
                yDim = this.yDim;
                zDim = this.zDim;
                return _byteData;
            }

            byte[] byteData = null;
            xDim = yDim = zDim = 0;
            if (!File.Exists(this.fileName))
            {
                gLogger.Instance.writeLog("Cannot open file: ");
                gLogger.Instance.writeLog(this.fileName);
                gLogger.Instance.writeLog("\n");
            }

            else
            {
                FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read);
                BinaryReader binReader = new BinaryReader(fs);
                binReader.BaseStream.Position = 0;

                try
                {
                    if (!isRaw)
                    {
                        try
                        {
                            xDim = Convert.ToInt32(ToByteToString(ReadTilWhiteSpace(ref binReader)));
                            yDim = Convert.ToInt32(ToByteToString(ReadTilWhiteSpace(ref binReader)));
                            zDim = Convert.ToInt32(ToByteToString(ReadTilWhiteSpace(ref binReader)));
                        }
                        catch (System.FormatException e)
                        {
                            xDim = this.xDim;
                            yDim = this.yDim;
                            zDim = this.zDim;
                            gLogger.Instance.writeLog(e.ToString() + "\n");
                        }
                    }
                    else
                    {
                        xDim = this.xDim;
                        yDim = this.yDim;
                        zDim = this.zDim;
                    }

                    gLogger.Instance.writeLog("The original data size is: X dim = " +
                                              xDim + "   Y dim = " + yDim + "   Z dim = " + zDim + "\n");
                    int x_Dim, y_Dim, z_Dim;
                    x_Dim = xDim; y_Dim = yDim; z_Dim = zDim;
                    int dataSize = x_Dim * y_Dim * z_Dim;
                    //int n_pow;
                    //int x_Dim, y_Dim, z_Dim;
                    //n_pow = (int)(System.Math.Ceiling(System.Math.Log((double)xDim) / System.Math.Log(2.0)));
                    //x_Dim = 2 << (n_pow - 1);
                    //n_pow = (int)(System.Math.Ceiling(System.Math.Log((double)yDim) / System.Math.Log(2.0)));
                    //y_Dim = 2 << (n_pow - 1);
                    //n_pow = (int)(System.Math.Ceiling(System.Math.Log((double)zDim) / System.Math.Log(2.0)));
                    //z_Dim = 2 << (n_pow - 1);

                    //gLogger.Instance.writeLog("The new data size is z: xdim = " + x_Dim +
                    //    "   ydim = " + y_Dim + "   zdim = " + z_Dim + "\n");

                    //int dataSize = x_Dim * y_Dim * z_Dim;
                    //xDim = x_Dim;
                    //yDim = y_Dim;
                    //zDim = z_Dim;

                    //read the data
                    int m, n, p;

                    int xmin = (x_Dim - xDim) / 2;
                    int ymin = (y_Dim - yDim) / 2;
                    int zmin = (z_Dim - zDim) / 2;

                    int d_min = 100000;
                    int d_max = -10000;

                    //this.PixelBufferType == BYTE
                    byteData = new byte[dataSize];
                    for (int i = 0; i < dataSize; i++)
                    {
                        byteData[i] = (byte)0;
                    }

                    byte val;

                    for (p = zmin; p < zDim + zmin; p++)
                    {
                        for (n = ymin; n < yDim + ymin; n++)
                        {
                            for (m = xmin; m < xDim + xmin; m++)
                            {
                                val = binReader.ReadByte();
                                byteData[p * y_Dim * x_Dim + n * x_Dim + m] = val;
                                if ((int)val > d_max) d_max = (int)val;
                                if ((int)val < d_min) d_min = (int)val;
                            }
                        }
                    }

                    gLogger.Instance.writeLog("The data range is " + d_min + "-" + d_max + "\n");
                }

                catch (EndOfStreamException e)
                {
                    gLogger.Instance.writeLog("{0} caught and ignored. " +
                        "Using default values." + e.GetType().Name);
                }
                finally
                {
                    binReader.Close();
                }
            }
            return byteData;

        }

        /// <summary>
        /// Reads the texture and returns texture data and the dimensions 
        /// </summary>
        /// <param name="xDim">x Dim</param>
        /// <param name="yDim">y Dim</param>
        /// <param name="zDim">z Dim</param>
        /// <returns>unsigned int array</returns>
        public override uint[] GetTextureInt(out int xDim, out int yDim, out int zDim)
        {

            uint[] intData = null;
            xDim = yDim = zDim = 0;
            if (!File.Exists(this.fileName))
            {
                gLogger.Instance.writeLog("Cannot open file: ");
                gLogger.Instance.writeLog(this.fileName);
                gLogger.Instance.writeLog("\n");
            }

            else
            {
                FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read);
                BinaryReader binReader = new BinaryReader(fs);
                binReader.BaseStream.Position = 0;

                try
                {
                    if (!isRaw)
                    {
                        try
                        {
                            xDim = Convert.ToInt32(ToByteToString(ReadTilWhiteSpace(ref binReader)));
                            yDim = Convert.ToInt32(ToByteToString(ReadTilWhiteSpace(ref binReader)));
                            zDim = Convert.ToInt32(ToByteToString(ReadTilWhiteSpace(ref binReader)));
                        }
                        catch (System.FormatException e)
                        {
                            xDim = this.xDim;
                            yDim = this.yDim;
                            zDim = this.zDim;
                            gLogger.Instance.writeLog(e.ToString() + "\n");
                        }
                    }
                    else
                    {
                        xDim = this.xDim;
                        yDim = this.yDim;
                        zDim = this.zDim;
                    }

                    gLogger.Instance.writeLog("The original data size is: X dim = " +
                                              xDim + "   Y dim = " + yDim + "   Z dim = " + zDim + "\n");

                    int n_pow;
                    int x_Dim, y_Dim, z_Dim;
                    n_pow = (int)(System.Math.Ceiling(System.Math.Log((double)xDim) / System.Math.Log(2.0)));
                    x_Dim = 2 << (n_pow - 1);
                    n_pow = (int)(System.Math.Ceiling(System.Math.Log((double)yDim) / System.Math.Log(2.0)));
                    y_Dim = 2 << (n_pow - 1);
                    n_pow = (int)(System.Math.Ceiling(System.Math.Log((double)zDim) / System.Math.Log(2.0)));
                    z_Dim = 2 << (n_pow - 1);

                    gLogger.Instance.writeLog("The new data size is z: xdim = " + x_Dim +
                        "   ydim = " + y_Dim + "   zdim = " + z_Dim + "\n");

                    int dataSize = x_Dim * y_Dim * z_Dim;

                    //read the data
                    int m, n, p;

                    int xmin = (x_Dim - xDim) / 2;
                    int ymin = (y_Dim - yDim) / 2;
                    int zmin = (z_Dim - zDim) / 2;

                    int d_min = 100000;
                    int d_max = -10000;

                    //this.PixelBufferType = INT  ( unsigned int)

                    intData = new uint[dataSize];
                    for (int i = 0; i < dataSize; i++)
                    {
                        intData[i] = (uint)0;
                    }
                    uint val;

                    for (p = zmin; p < zDim + zmin; p++)
                    {
                        for (n = ymin; n < yDim + ymin; n++)
                        {
                            for (m = xmin; m < xDim + xmin; m++)
                            {
                                val = binReader.ReadUInt32();
                                intData[p * y_Dim * x_Dim + n * x_Dim + m] = val;
                                if ((int)val > d_max) d_max = (int)val;
                                if ((int)val < d_min) d_min = (int)val;
                            }
                        }
                    }

                    gLogger.Instance.writeLog("The data range is " + d_min + "-" + d_max + "\n");
                }


                catch (EndOfStreamException e)
                {
                    gLogger.Instance.writeLog("{0} caught and ignored. " +
                        "Using default values." + e.GetType().Name);
                }
                finally
                {
                    binReader.Close();
                }
            }
            return intData;

        }

        //public override ushort[] GetTextureShort(out int xDim, out int yDim, out int zDim)
        /// <summary>
        /// Reads the texture and returns texture data and the dimensions 
        /// </summary>
        /// <param name="xDim">x Dim</param>
        /// <param name="yDim">y Dim</param>
        /// <param name="zDim">z Dim</param>
        /// <returns>byte array</returns>  
        public override byte[] GetTextureShort(out int xDim, out int yDim, out int zDim)
          {
            ushort[] shortData = null;
            byte[] byteData = null;
            xDim = yDim = zDim = 0;
            if (!File.Exists(this.fileName))
            {
                gLogger.Instance.writeLog("Cannot open file: ");
                gLogger.Instance.writeLog(this.fileName);
                gLogger.Instance.writeLog("\n");
            }

            else
            {
                FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read);
                BinaryReader binReader = new BinaryReader(fs);
                binReader.BaseStream.Position = 0;

                try
                {
                    if (!isRaw)
                    {
                        try
                        {
                            if (extension == ".vol")
                            {
                                xDim = Convert.ToInt32(ToByteToString(ReadTilWhiteSpace(ref binReader)));
                                yDim = Convert.ToInt32(ToByteToString(ReadTilWhiteSpace(ref binReader)));
                                zDim = Convert.ToInt32(ToByteToString(ReadTilWhiteSpace(ref binReader)));
                            }
                            else if (extension == ".dat")
                            {
                                xDim = binReader.ReadUInt16();
                                yDim = binReader.ReadUInt16();
                                zDim = binReader.ReadUInt16();
                            }
                        }
                        catch (System.FormatException e)
                        {
                            xDim = this.xDim;
                            yDim = this.yDim;
                            zDim = this.zDim;
                            gLogger.Instance.writeLog(e.ToString() + "\n");
                        }
                    }
                    else
                    {
                        xDim = this.xDim;
                        yDim = this.yDim;
                        zDim = this.zDim;
                    }

                    gLogger.Instance.writeLog("The original data size is: X dim = " +
                                              xDim + "   Y dim = " + yDim + "   Z dim = " + zDim + "\n");

                    int n_pow;
                    int x_Dim, y_Dim, z_Dim;
                    n_pow = (int)(System.Math.Ceiling(System.Math.Log((double)xDim) / System.Math.Log(2.0)));
                    x_Dim = 2 << (n_pow - 1);
                    n_pow = (int)(System.Math.Ceiling(System.Math.Log((double)yDim) / System.Math.Log(2.0)));
                    y_Dim = 2 << (n_pow - 1);
                    n_pow = (int)(System.Math.Ceiling(System.Math.Log((double)zDim) / System.Math.Log(2.0)));
                    z_Dim = 2 << (n_pow - 1);

                    gLogger.Instance.writeLog("The new data size is z: xdim = " + x_Dim +
                        "   ydim = " + y_Dim + "   zdim = " + z_Dim + "\n");

                    int dataSize = x_Dim * y_Dim * z_Dim;

                    //read the data
                    int m, n, p;

                    int xmin = (x_Dim - xDim) / 2;
                    int ymin = (y_Dim - yDim) / 2;
                    int zmin = (z_Dim - zDim) / 2;

                    int d_min = 100000;
                    int d_max = -10000;

                    //this.PixelBufferType == SHORT
                    shortData = new ushort[dataSize];
                    byteData = new byte[dataSize];

                    for (int i = 0; i < dataSize; i++)
                    {
                        shortData[i] = (ushort)0;
                        byteData[i] = (byte)0;
                    }

                    ushort val = 0;

                    byte min_byte_val = 255, max_byte_val = 0;
                    for (p = zmin; p < zDim + zmin; p++)
                    {
                        for (n = ymin; n < yDim + ymin; n++)
                        {
                            for (m = xmin; m < xDim + xmin; m++)
                            {
                                val = binReader.ReadUInt16();
                                shortData[p * y_Dim * x_Dim + n * x_Dim + m] = val;
                                // val varies from 0 to 4095  --> compress it to 0 to 255
                                //int fract = val/4095;//16 bits mean 65536 - why 4095 here?
                                float fract = val / 65535.0f;
                                byte byteval = Convert.ToByte(fract * 255);
                                byteval += (byte)100;//why this is necessary?
                                byteData[p* y_Dim * x_Dim + n * x_Dim +m] = byteval;
                                if ((int)val > d_max) d_max = (int)val;
                                if ((int)val < d_min) d_min = (int)val;

                                if (byteval > max_byte_val) max_byte_val = byteval;
                                if (byteval < min_byte_val) min_byte_val = byteval;
                                //Console.WriteLine(byteval);//debug
                            }
                        }
                    }

                    gLogger.Instance.writeLog("The data range is " + d_min + "-" + d_max + "\n");
                }

                catch (EndOfStreamException e)
                {
                    gLogger.Instance.writeLog("{0} caught and ignored. " +
                        "Using default values." + e.GetType().Name);
                }
                finally
                {
                    binReader.Close();
                }
            }
            //return shortData;
            return byteData;

        }

        /// <summary>
        /// Reads the texture and returns texture data and the dimensions 
        /// </summary>
        /// <param name="xDim">x Dim</param>
        /// <param name="yDim">y Dim</param>
        /// <param name="zDim">z Dim</param>
        /// <returns>float array</returns>
        public override float[] GetTextureFloat(out int xDim, out int yDim, out int zDim)
        {

            float[] floatData = null;
            xDim = yDim = zDim = 0;
            if (!File.Exists(this.fileName))
            {
                gLogger.Instance.writeLog("Cannot open file: ");
                gLogger.Instance.writeLog(this.fileName);
                gLogger.Instance.writeLog("\n");
            }

            else
            {
                FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read);
                BinaryReader binReader = new BinaryReader(fs);
                binReader.BaseStream.Position = 0;

                try
                {
                    if (!isRaw)
                    {
                        try
                        {
                            xDim = Convert.ToInt32(ToByteToString(ReadTilWhiteSpace(ref binReader)));
                            yDim = Convert.ToInt32(ToByteToString(ReadTilWhiteSpace(ref binReader)));
                            zDim = Convert.ToInt32(ToByteToString(ReadTilWhiteSpace(ref binReader)));
                        }
                        catch (System.FormatException e)
                        {
                            xDim = this.xDim;
                            yDim = this.yDim;
                            zDim = this.zDim;
                            gLogger.Instance.writeLog(e.ToString() + "\n");
                        }
                    }
                    else
                    {
                        xDim = this.xDim;
                        yDim = this.yDim;
                        zDim = this.zDim;
                    }

                    gLogger.Instance.writeLog("The original data size is: X dim = " +
                                              xDim + "   Y dim = " + yDim + "   Z dim = " + zDim + "\n");

                    int n_pow;
                    int x_Dim, y_Dim, z_Dim;
                    n_pow = (int)(System.Math.Ceiling(System.Math.Log((double)xDim) / System.Math.Log(2.0)));
                    x_Dim = 2 << (n_pow - 1);
                    n_pow = (int)(System.Math.Ceiling(System.Math.Log((double)yDim) / System.Math.Log(2.0)));
                    y_Dim = 2 << (n_pow - 1);
                    n_pow = (int)(System.Math.Ceiling(System.Math.Log((double)zDim) / System.Math.Log(2.0)));
                    z_Dim = 2 << (n_pow - 1);

                    gLogger.Instance.writeLog("The new data size is z: xdim = " + x_Dim +
                        "   ydim = " + y_Dim + "   zdim = " + z_Dim + "\n");

                    int dataSize = x_Dim * y_Dim * z_Dim;

                    //read the data
                    int m, n, p;

                    int xmin = (x_Dim - xDim) / 2;
                    int ymin = (y_Dim - yDim) / 2;
                    int zmin = (z_Dim - zDim) / 2;

                    int d_min = 100000;
                    int d_max = -10000;

                    //this.PixelBufferType == FLOAT

                    floatData = new float[dataSize];
                    for (int i = 0; i < dataSize; i++)
                    {
                        floatData[i] = (float)0;
                    }
                    float val;
                    for (p = zmin; p < zDim + zmin; p++)
                    {
                        for (n = ymin; n < yDim + ymin; n++)
                        {
                            for (m = xmin; m < xDim + xmin; m++)
                            {
                                val = binReader.ReadSingle();
                                floatData[p * y_Dim * x_Dim + n * x_Dim + m] = val;
                                if ((int)val > d_max) d_max = (int)val;
                                if ((int)val < d_min) d_min = (int)val;
                            }
                        }
                    }
                    gLogger.Instance.writeLog("The data range is " + d_min + "-" + d_max + "\n");
                }
                catch (EndOfStreamException e)
                {
                    gLogger.Instance.writeLog("{0} caught and ignored. " +
                        "Using default values." + e.GetType().Name);
                }
                finally
                {
                    binReader.Close();
                }
            }
            return floatData;
        }
    }

    //
    // Class Volume Loader
    //
    /*public class VolumeLoader
    {
        Volume vol;
        uint texID;
        static int volFileCount = 1;

        //
        //Constructors
        //
        public VolumeLoader(int xDim, int yDim, int zDim, String filename, Texture.PixelBufferType pixelType, bool isRaw)
        {
            String TexName = "VolumeTexture";
            String name = TexName + Convert.ToString(volFileCount);
            vol = (Volume)new Volume(xDim, yDim, zDim, filename, name, pixelType, Texture.TextureWrapType.CLAMP, Texture.PixelFormatType.ALPHA, isRaw);
        }

        public VolumeLoader(String filename)
        {
            String TexName = "VolumeTexture";
            String name = TexName + Convert.ToString(volFileCount);
            // Default is VOL file
            vol = (Volume)new Volume(filename, name, false);
        }

        // Loads the 3D texture and returns the texture ID
        public uint LoadVolume(Volume volInfo)
        {
            //TextureManager TexManager = TextureManager.Instance;
            //texID = TexManager.GetTextureID(volInfo);
            return texID;
        }

        public Volume Vol
        {
            get { return vol; }
        }
    }*/

}